package com.aps.demo;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * 设备（机器）类
 * <p>
 * 在APS排程系统中，设备是执行工序的物理资源。
 * 每台设备在同一时间只能执行一个工序，需要跟踪其时间占用情况以避免冲突。
 * <p>
 * 业务场景：
 * - 车间有3台设备：M1（车床）、M2（铣床）、M3（磨床）
 * - 每台设备都有自己的工作时间安排和可用性状态
 * - 需要确保同一台设备在同一时间不被分配给多个工序
 *
 * @author APS System
 * @version 1.0
 */
public class Machine {

    /**
     * 时间格式化器
     */
    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

    /**
     * 设备唯一标识符
     */
    private String id;

    /**
     * 设备类型
     */
    private String type;

    /**
     * 设备下一次可用时间（最早可以开始新工序的时间）
     */
    private LocalDateTime nextAvailableTime;

    /**
     * 设备已安排的工序时间段列表，用于冲突检测和利用率计算
     */
    private List<Interval> scheduledIntervals;
    /**
     * 设备负载因子，用于均衡负载
     */
    private int loadFactor = 0;
    /**
     * 设备效率系数，1.0为标准，值越小效率越高
     */
    private double efficiencyFactor = 1.0;


    /**
     * 构造函数
     *
     * @param id                   设备ID
     * @param initialAvailableTime 初始可用时间
     */
    public Machine(String id, String type, LocalDateTime initialAvailableTime) {
        if (id == null || id.trim().isEmpty()) {
            throw new IllegalArgumentException("设备ID不能为空");
        }
        if (type == null || type.trim().isEmpty()) {
            throw new IllegalArgumentException("设备类型不能为空");
        }
        if (initialAvailableTime == null) {
            throw new IllegalArgumentException("初始可用时间不能为空");
        }
        this.type = type;
        this.id = id;
        this.nextAvailableTime = initialAvailableTime;
        this.scheduledIntervals = new ArrayList<>();
    }

    /**
     * 重置设备可用时间
     *
     * @param startTime 重置到的起始时间
     */
    public void resetAvailability(LocalDateTime startTime) {
        this.nextAvailableTime = startTime;
        this.clearIntervals();
    }

    /**
     * 检查指定时间区间是否与已有安排冲突
     * <p>
     * 冲突检测逻辑：
     * - 如果新区间的结束时间早于某个已安排区间的开始时间，则无冲突
     * - 如果新区间的开始时间晚于某个已安排区间的结束时间，则无冲突
     * - 其他情况都存在冲突
     *
     * @param start 要检查的开始时间
     * @param end   要检查的结束时间
     * @return 如果有冲突返回true，否则返回false
     */
    public boolean hasConflict(LocalDateTime start, LocalDateTime end) {
        if (start.isAfter(end)) {
            throw new IllegalArgumentException("开始时间不能晚于结束时间");
        }

        return scheduledIntervals.stream()
                .anyMatch(interval ->
                        end.isAfter(interval.getStart()) && start.isBefore(interval.getEnd())
                );
    }

    /**
     * 添加时间区间到设备的已安排列表中
     * <p>
     * 该方法会：
     * 1. 将新的时间区间添加到已安排列表
     * 2. 保持列表按开始时间排序
     * 3. 更新设备的下一次可用时间
     *
     * @param start 开始时间
     * @param end   结束时间
     */
    public void addInterval(LocalDateTime start, LocalDateTime end) {
        if (start.isAfter(end)) {
            throw new IllegalArgumentException("开始时间不能晚于结束时间");
        }

        // 添加新的时间区间
        scheduledIntervals.add(new Interval(start, end));

        // 保持区间有序，便于冲突检测和利用率计算
        scheduledIntervals.sort(Comparator.comparing(Interval::getStart));

        // 更新下一次可用时间：取所有已安排区间结束时间的最大值
        LocalDateTime maxEndTime = scheduledIntervals.stream()
                .map(Interval::getEnd)
                .max(LocalDateTime::compareTo)
                .orElse(nextAvailableTime);

        if (maxEndTime.isAfter(nextAvailableTime)) {
            nextAvailableTime = maxEndTime;
        }
    }

    /**
     * 清除所有已安排的时间区间
     * <p>
     * 通常在重新计算排产方案时使用，将设备状态重置为初始状态。
     */
    public void clearIntervals() {
        scheduledIntervals.clear();
    }

    /**
     * 重置设备到指定时间点的可用状态
     *
     * @param resetTime 重置时间点
     */
    public void resetToTime(LocalDateTime resetTime) {
        clearIntervals();
        this.nextAvailableTime = resetTime;
    }

    /**
     * 计算设备在指定时间段内的利用率
     *
     * @param periodStart 时间段开始
     * @param periodEnd   时间段结束
     * @return 利用率（0.0-1.0之间）
     */
    public double getUtilizationRate(LocalDateTime periodStart, LocalDateTime periodEnd) {
        if (periodStart.isAfter(periodEnd)) {
            throw new IllegalArgumentException("时间段开始时间不能晚于结束时间");
        }

        long totalPeriodMinutes = java.time.Duration.between(periodStart, periodEnd).toMinutes();
        if (totalPeriodMinutes == 0) {
            return 0.0;
        }

        long busyMinutes = scheduledIntervals.stream()
                .mapToLong(interval -> {
                    // 计算区间与指定时间段的重叠部分
                    LocalDateTime overlapStart = interval.getStart().isBefore(periodStart) ?
                            periodStart : interval.getStart();
                    LocalDateTime overlapEnd = interval.getEnd().isAfter(periodEnd) ?
                            periodEnd : interval.getEnd();

                    if (overlapStart.isBefore(overlapEnd)) {
                        return java.time.Duration.between(overlapStart, overlapEnd).toMinutes();
                    }
                    return 0;
                })
                .sum();

        return (double) busyMinutes / totalPeriodMinutes;
    }

    /**
     * 获取设备在指定时间段内的空闲时间段列表
     *
     * @param periodStart 时间段开始
     * @param periodEnd   时间段结束
     * @return 空闲时间段列表
     */
    public List<Interval> getIdleIntervals(LocalDateTime periodStart, LocalDateTime periodEnd) {
        List<Interval> idleIntervals = new ArrayList<>();

        if (periodStart.isAfter(periodEnd)) {
            return idleIntervals;
        }

        // 过滤出与指定时间段有重叠的已安排区间
        List<Interval> relevantIntervals = scheduledIntervals.stream()
                .filter(interval -> !interval.getEnd().isBefore(periodStart) &&
                        !interval.getStart().isAfter(periodEnd))
                .sorted(Comparator.comparing(Interval::getStart))
                .collect(java.util.stream.Collectors.toList());

        LocalDateTime currentTime = periodStart;

        for (Interval interval : relevantIntervals) {
            LocalDateTime intervalStart = interval.getStart().isBefore(periodStart) ?
                    periodStart : interval.getStart();

            // 如果当前时间早于区间开始时间，说明有空闲时间
            if (currentTime.isBefore(intervalStart)) {
                idleIntervals.add(new Interval(currentTime, intervalStart));
            }

            // 更新当前时间到区间结束时间
            LocalDateTime intervalEnd = interval.getEnd().isAfter(periodEnd) ?
                    periodEnd : interval.getEnd();
            currentTime = intervalEnd.isAfter(currentTime) ? intervalEnd : currentTime;
        }

        // 检查最后是否还有空闲时间
        if (currentTime.isBefore(periodEnd)) {
            idleIntervals.add(new Interval(currentTime, periodEnd));
        }

        return idleIntervals;
    }

    // Getter和Setter方法

    /**
     * 获取设备ID
     *
     * @return 设备ID
     */
    public String getId() {
        return id;
    }

    /**
     * 设置设备ID
     *
     * @param id 设备ID
     */
    public void setId(String id) {
        this.id = id;
    }

    /**
     * 获取下一次可用时间
     *
     * @return 下一次可用时间
     */
    public LocalDateTime getNextAvailableTime() {
        return nextAvailableTime;
    }

    /**
     * 获取设备类型
     *
     * @return 设备类型
     */
    public String getType() {
        return type;
    }

    /**
     * 设置设备类型
     *
     * @param type 设备类型
     */
    public void setType(String type) {
        this.type = type;
    }

    /**
     * 设置下一次可用时间
     *
     * @param nextAvailableTime 下一次可用时间
     */
    public void setNextAvailableTime(LocalDateTime nextAvailableTime) {
        this.nextAvailableTime = nextAvailableTime;
    }

    /**
     * 获取已安排的时间区间列表
     *
     * @return 已安排的时间区间列表
     */
    public List<Interval> getScheduledIntervals() {
        return new ArrayList<>(scheduledIntervals); // 返回副本，防止外部修改
    }

    /**
     * 获取设备负载因子
     *
     * @return 设备负载因子
     */
    public int getLoadFactor() {
        return loadFactor;
    }

    /**
     * 获取设备效率因子
     *
     * @return 设备效率因子
     */
    public double getEfficiencyFactor() {
        return efficiencyFactor;
    }

    /**
     * toString方法，便于调试和日志输出
     *
     * @return 格式化的字符串表示
     */
    @Override
    public String toString() {
        return String.format("设备[%s]: 下次可用=%s, 已安排区间数=%d",
                id,
                nextAvailableTime.format(formatter),
                scheduledIntervals.size());
    }

    /**
     * equals方法，基于设备ID进行比较
     *
     * @param obj 要比较的对象
     * @return 如果设备ID相同返回true，否则返回false
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;

        Machine machine = (Machine) obj;
        return id != null ? id.equals(machine.id) : machine.id == null;
    }

    /**
     * hashCode方法，基于设备ID
     *
     * @return 散列值
     */
    @Override
    public int hashCode() {
        return id != null ? id.hashCode() : 0;
    }
}
